1   /*
2    * Copyright (c) 1996, 2007, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package java.awt;
26  
27  import java.awt.AWTException;
28  import java.awt.Point;
29  import java.awt.Toolkit;
30  
31  import java.io.File;
32  import java.io.FileInputStream;
33  
34  import java.beans.ConstructorProperties;
35  import java.util.Hashtable;
36  import java.util.Properties;
37  import java.util.StringTokenizer;
38  
39  import java.security.AccessController;
40  
41  import sun.util.logging.PlatformLogger;
42  
43  /**
44   * A class to encapsulate the bitmap representation of the mouse cursor.
45   *
46   * @see Component#setCursor
47   * @author      Amy Fowler
48   */
49  public class Cursor implements java.io.Serializable {
50  
51      /**
52       * The default cursor type (gets set if no cursor is defined).
53       */
54      public static final int     DEFAULT_CURSOR                  = 0;
55  
56      /**
57       * The crosshair cursor type.
58       */
59      public static final int     CROSSHAIR_CURSOR                = 1;
60  
61      /**
62       * The text cursor type.
63       */
64      public static final int     TEXT_CURSOR                     = 2;
65  
66      /**
67       * The wait cursor type.
68       */
69      public static final int     WAIT_CURSOR                     = 3;
70  
71      /**
72       * The south-west-resize cursor type.
73       */
74      public static final int     SW_RESIZE_CURSOR                = 4;
75  
76      /**
77       * The south-east-resize cursor type.
78       */
79      public static final int     SE_RESIZE_CURSOR                = 5;
80  
81      /**
82       * The north-west-resize cursor type.
83       */
84      public static final int     NW_RESIZE_CURSOR                = 6;
85  
86      /**
87       * The north-east-resize cursor type.
88       */
89      public static final int     NE_RESIZE_CURSOR                = 7;
90  
91      /**
92       * The north-resize cursor type.
93       */
94      public static final int     N_RESIZE_CURSOR                 = 8;
95  
96      /**
97       * The south-resize cursor type.
98       */
99      public static final int     S_RESIZE_CURSOR                 = 9;
100 
101     /**
102      * The west-resize cursor type.
103      */
104     public static final int     W_RESIZE_CURSOR                 = 10;
105 
106     /**
107      * The east-resize cursor type.
108      */
109     public static final int     E_RESIZE_CURSOR                 = 11;
110 
111     /**
112      * The hand cursor type.
113      */
114     public static final int     HAND_CURSOR                     = 12;
115 
116     /**
117      * The move cursor type.
118      */
119     public static final int     MOVE_CURSOR                     = 13;
120 
121     /**
122       * @deprecated As of JDK version 1.7, the {@link #getPredefinedCursor(int)}
123       * method should be used instead.
124       */
125     @Deprecated
126     protected static Cursor predefined[] = new Cursor[14];
127 
128     /**
129      * This field is a private replacement for 'predefined' array.
130      */
131     private final static Cursor[] predefinedPrivate = new Cursor[14];
132 
133     /* Localization names and default values */
134     static final String[][] cursorProperties = {
135         { "AWT.DefaultCursor", "Default Cursor" },
136         { "AWT.CrosshairCursor", "Crosshair Cursor" },
137         { "AWT.TextCursor", "Text Cursor" },
138         { "AWT.WaitCursor", "Wait Cursor" },
139         { "AWT.SWResizeCursor", "Southwest Resize Cursor" },
140         { "AWT.SEResizeCursor", "Southeast Resize Cursor" },
141         { "AWT.NWResizeCursor", "Northwest Resize Cursor" },
142         { "AWT.NEResizeCursor", "Northeast Resize Cursor" },
143         { "AWT.NResizeCursor", "North Resize Cursor" },
144         { "AWT.SResizeCursor", "South Resize Cursor" },
145         { "AWT.WResizeCursor", "West Resize Cursor" },
146         { "AWT.EResizeCursor", "East Resize Cursor" },
147         { "AWT.HandCursor", "Hand Cursor" },
148         { "AWT.MoveCursor", "Move Cursor" },
149     };
150 
151     /**
152      * The chosen cursor type initially set to
153      * the <code>DEFAULT_CURSOR</code>.
154      *
155      * @serial
156      * @see #getType()
157      */
158     int type = DEFAULT_CURSOR;
159 
160     /**
161      * The type associated with all custom cursors.
162      */
163     public static final int     CUSTOM_CURSOR                   = -1;
164 
165     /*
166      * hashtable, filesystem dir prefix, filename, and properties for custom cursors support
167      */
168 
169     private static final Hashtable  systemCustomCursors         = new Hashtable(1);
170     private static final String systemCustomCursorDirPrefix = initCursorDir();
171 
172     private static String initCursorDir() {
173         String jhome =  (String) java.security.AccessController.doPrivileged(
174                new sun.security.action.GetPropertyAction("java.home"));
175         return jhome +
176             File.separator + "lib" + File.separator + "images" +
177             File.separator + "cursors" + File.separator;
178     }
179 
180     private static final String     systemCustomCursorPropertiesFile = systemCustomCursorDirPrefix + "cursors.properties";
181 
182     private static       Properties systemCustomCursorProperties = null;
183 
184     private static final String CursorDotPrefix  = "Cursor.";
185     private static final String DotFileSuffix    = ".File";
186     private static final String DotHotspotSuffix = ".HotSpot";
187     private static final String DotNameSuffix    = ".Name";
188 
189     /*
190      * JDK 1.1 serialVersionUID
191      */
192     private static final long serialVersionUID = 8028237497568985504L;
193 
194     private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.Cursor");
195 
196     static {
197         /* ensure that the necessary native libraries are loaded */
198         Toolkit.loadLibraries();
199         if (!GraphicsEnvironment.isHeadless()) {
200             initIDs();
201         }
202     }
203 
204     /**
205      * Initialize JNI field and method IDs for fields that may be
206      * accessed from C.
207      */
208     private static native void initIDs();
209 
210     /**
211      * Hook into native data.
212      */
213     private transient long pData;
214 
215     private transient Object anchor = new Object();
216 
217     static class CursorDisposer implements sun.java2d.DisposerRecord {
218         volatile long pData;
219         public CursorDisposer(long pData) {
220             this.pData = pData;
221         }
222         public void dispose() {
223             if (pData != 0) {
224                 finalizeImpl(pData);
225             }
226         }
227     }
228     transient CursorDisposer disposer;
229     private void setPData(long pData) {
230         this.pData = pData;
231         if (GraphicsEnvironment.isHeadless()) {
232             return;
233         }
234         if (disposer == null) {
235             disposer = new CursorDisposer(pData);
236             // anchor is null after deserialization
237             if (anchor == null) {
238                 anchor = new Object();
239             }
240             sun.java2d.Disposer.addRecord(anchor, disposer);
241         } else {
242             disposer.pData = pData;
243         }
244     }
245 
246     /**
247      * The user-visible name of the cursor.
248      *
249      * @serial
250      * @see #getName()
251      */
252     protected String name;
253 
254     /**
255      * Returns a cursor object with the specified predefined type.
256      *
257      * @param type the type of predefined cursor
258      * @return the specified predefined cursor
259      * @throws IllegalArgumentException if the specified cursor type is
260      *         invalid
261      */
262     static public Cursor getPredefinedCursor(int type) {
263         if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) {
264             throw new IllegalArgumentException("illegal cursor type");
265         }
266         Cursor c = predefinedPrivate[type];
267         if (c == null) {
268             predefinedPrivate[type] = c = new Cursor(type);
269         }
270         // fill 'predefined' array for backwards compatibility.
271         if (predefined[type] == null) {
272             predefined[type] = c;
273         }
274         return c;
275     }
276 
277     /**
278      * Returns a system-specific custom cursor object matching the
279      * specified name.  Cursor names are, for example: "Invalid.16x16"
280      *
281      * @param name a string describing the desired system-specific custom cursor
282      * @return the system specific custom cursor named
283      * @exception HeadlessException if
284      * <code>GraphicsEnvironment.isHeadless</code> returns true
285      */
286     static public Cursor getSystemCustomCursor(final String name)
287         throws AWTException, HeadlessException {
288         GraphicsEnvironment.checkHeadless();
289         Cursor cursor = (Cursor)systemCustomCursors.get(name);
290 
291         if (cursor == null) {
292             synchronized(systemCustomCursors) {
293                 if (systemCustomCursorProperties == null)
294                     loadSystemCustomCursorProperties();
295             }
296 
297             String prefix = CursorDotPrefix + name;
298             String key    = prefix + DotFileSuffix;
299 
300             if (!systemCustomCursorProperties.containsKey(key)) {
301                 if (log.isLoggable(PlatformLogger.FINER)) {
302                     log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null");
303                 }
304                 return null;
305             }
306 
307             final String fileName =
308                 systemCustomCursorProperties.getProperty(key);
309 
310             String localized = (String)systemCustomCursorProperties.getProperty(prefix + DotNameSuffix);
311 
312             if (localized == null) localized = name;
313 
314             String hotspot = (String)systemCustomCursorProperties.getProperty(prefix + DotHotspotSuffix);
315 
316             if (hotspot == null)
317                 throw new AWTException("no hotspot property defined for cursor: " + name);
318 
319             StringTokenizer st = new StringTokenizer(hotspot, ",");
320 
321             if (st.countTokens() != 2)
322                 throw new AWTException("failed to parse hotspot property for cursor: " + name);
323 
324             int x = 0;
325             int y = 0;
326 
327             try {
328                 x = Integer.parseInt(st.nextToken());
329                 y = Integer.parseInt(st.nextToken());
330             } catch (NumberFormatException nfe) {
331                 throw new AWTException("failed to parse hotspot property for cursor: " + name);
332             }
333 
334             try {
335                 final int fx = x;
336                 final int fy = y;
337                 final String flocalized = localized;
338 
339                 cursor = (Cursor) java.security.AccessController.doPrivileged(
340                     new java.security.PrivilegedExceptionAction() {
341                     public Object run() throws Exception {
342                         Toolkit toolkit = Toolkit.getDefaultToolkit();
343                         Image image = toolkit.getImage(
344                            systemCustomCursorDirPrefix + fileName);
345                         return toolkit.createCustomCursor(
346                                     image, new Point(fx,fy), flocalized);
347                     }
348                 });
349             } catch (Exception e) {
350                 throw new AWTException(
351                     "Exception: " + e.getClass() + " " + e.getMessage() +
352                     " occurred while creating cursor " + name);
353             }
354 
355             if (cursor == null) {
356                 if (log.isLoggable(PlatformLogger.FINER)) {
357                     log.finer("Cursor.getSystemCustomCursor(" + name + ") returned null");
358                 }
359             } else {
360                 systemCustomCursors.put(name, cursor);
361             }
362         }
363 
364         return cursor;
365     }
366 
367     /**
368      * Return the system default cursor.
369      */
370     static public Cursor getDefaultCursor() {
371         return getPredefinedCursor(Cursor.DEFAULT_CURSOR);
372     }
373 
374     /**
375      * Creates a new cursor object with the specified type.
376      * @param type the type of cursor
377      * @throws IllegalArgumentException if the specified cursor type
378      * is invalid
379      */
380     @ConstructorProperties({"type"})
381     public Cursor(int type) {
382         if (type < Cursor.DEFAULT_CURSOR || type > Cursor.MOVE_CURSOR) {
383             throw new IllegalArgumentException("illegal cursor type");
384         }
385         this.type = type;
386 
387         // Lookup localized name.
388         name = Toolkit.getProperty(cursorProperties[type][0],
389                                    cursorProperties[type][1]);
390     }
391 
392     /**
393      * Creates a new custom cursor object with the specified name.<p>
394      * Note:  this constructor should only be used by AWT implementations
395      * as part of their support for custom cursors.  Applications should
396      * use Toolkit.createCustomCursor().
397      * @param name the user-visible name of the cursor.
398      * @see java.awt.Toolkit#createCustomCursor
399      */
400     protected Cursor(String name) {
401         this.type = Cursor.CUSTOM_CURSOR;
402         this.name = name;
403     }
404 
405     /**
406      * Returns the type for this cursor.
407      */
408     public int getType() {
409         return type;
410     }
411 
412     /**
413      * Returns the name of this cursor.
414      * @return    a localized description of this cursor.
415      * @since     1.2
416      */
417     public String getName() {
418         return name;
419     }
420 
421     /**
422      * Returns a string representation of this cursor.
423      * @return    a string representation of this cursor.
424      * @since     1.2
425      */
426     public String toString() {
427         return getClass().getName() + "[" + getName() + "]";
428     }
429 
430     /*
431      * load the cursor.properties file
432      */
433     private static void loadSystemCustomCursorProperties() throws AWTException {
434         synchronized(systemCustomCursors) {
435             systemCustomCursorProperties = new Properties();
436 
437             try {
438                 AccessController.doPrivileged(
439                       new java.security.PrivilegedExceptionAction() {
440                     public Object run() throws Exception {
441                         FileInputStream fis = null;
442                         try {
443                             fis = new FileInputStream(
444                                            systemCustomCursorPropertiesFile);
445                             systemCustomCursorProperties.load(fis);
446                         } finally {
447                             if (fis != null)
448                                 fis.close();
449                         }
450                         return null;
451                     }
452                 });
453             } catch (Exception e) {
454                 systemCustomCursorProperties = null;
455                  throw new AWTException("Exception: " + e.getClass() + " " +
456                    e.getMessage() + " occurred while loading: " +
457                                         systemCustomCursorPropertiesFile);
458             }
459         }
460     }
461 
462     private native static void finalizeImpl(long pData);
463 }